package com.jerry.spring.security.jwt.filter;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.jerry.spring.security.jwt.constant.RedisConstant;
import com.jerry.spring.security.jwt.domain.LoginUser;
import com.jerry.spring.security.jwt.util.JwtUtil;
import com.jerry.spring.security.jwt.util.RedisUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 认证过滤器
 *
 * @author jerry 2024/5/9 22:12
 */
@Slf4j
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Resource
    private RedisUtil redisUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取token
        String token = request.getHeader("token");
        if (StrUtil.isBlank(token)) {
            // 如果token为空，那么就放行，即使未认证的请求，也会有后面的过滤器进行拦截
            filterChain.doFilter(request, response);
            return;
        }

        // 解析token获取userId
        String userId;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            log.error("token非法");
            throw new RuntimeException("token非法");
        }

        // 从Redis中获取用户信息
        LoginUser loginUser = redisUtil.getObject(RedisConstant.LOGIN_KEY + userId);
        if (ObjUtil.isNull(loginUser)) {
            log.error("未找到user信息：{}", userId);
            throw new RuntimeException("用户未登录");
        }

        // 存入SecurityContextHolder中
        // 注意这里要使用3个参数的构造方法，因为这个构造方法会将是否认证设置为true。并且因为已经认证通过了，那么第一个参数可以传递实体对象，方便后面信息获取
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser,
                null, loginUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(request, response);
    }
}
